/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		ntds ldap enum sid.cpp
 * PURPOSE:		NTDS over LDAP support
 *				trustee SID enumeration functions
 */

namespace Ldap
{
using namespace Lookup;

template <class T> class ContainerEnumerator : public Enumerator<T>
{
protected:
	auto_refct<LdapConnection> Lc;
	auto_szpod<WCHAR> BaseDn;

	//in this case, you must still initialize Lc
	ContainerEnumerator() :
		BaseDn(NULL)
	{}

	ContainerEnumerator(LdapConnection *Lc, PCWSTR BaseDn) :
		Lc(Lc),
		BaseDn(BaseDn)
	{}

	virtual ContainerEnumerator<T> *CreateChild(PCWSTR BaseDn) = 0;

public:
	virtual void WalkChildren(ChildrenWalkDest<SID> &Dest) override
	{
		PWSTR Attrs[] = {L"cn", L"ou", L"distinguishedName", NULL};
		LDAPControl *ServerCtrls[] = {&DomainScopeHintCtrl, NULL};

		ULONG OpNum = StartLdapSearchEx(Lc, LDAP_SCOPE_ONELEVEL, Attrs, ServerCtrls, NULL, BaseDn, SbtLiteral, L"(distinguishedName=*)");

		auto_free<LDAPMessage, &free_ldap_message> SearchResult;

		AwaitSearchResult(Lc, OpNum, SearchResult);

		LDAPMessage *Entry = ldap_first_entry(Lc->ld, SearchResult);

		while(Entry)
		{
			auto_free<PWSTR, free_ldap_value> CnValues = auto_take_(ldap_get_values(Lc->ld, Entry, L"cn"));
			auto_free<PWSTR, free_ldap_value> OuValues = auto_take_(ldap_get_values(Lc->ld, Entry, L"ou"));
			auto_free<PWSTR, free_ldap_value> DnValues = auto_take_(ldap_get_values(Lc->ld, Entry, L"distinguishedName"));

			if(DnValues) //if, somehow, this mandatory attribute is missing from the entry, skip it
			{
				auto_refct<ContainerEnumerator> Child = CreateChild(DnValues[0]);
				Dest.ChildProc(CnValues ? CnValues[0] : OuValues[0], Child);
			}
			Entry = ldap_next_entry(Lc->ld, Entry);
		}
	}
};

class AccountEnumerator : public ContainerEnumerator<SID>
{
public:
	AccountEnumerator(LdapConnection *Lc, PCWSTR BaseDn) :
		ContainerEnumerator(Lc, BaseDn)
	{
		if(!BaseDn)
			this->BaseDn = Lc->DomainDc;
	}

	AccountEnumerator(NtdsLdapModuleBase *Module, DOMAIN_INFO const &Di)
	{
		auto_SDE_OPTS *Opts = &Module->ModOpts;
		PCWSTR Host = Di.Host, ForestName = Di.ForestName;
		auto_ptr<DOMAIN_CONTROLLER_INFO, Netapi::NetapiHeap> DcInfo;

		if(Di.Flags & DOMAIN_INFO_LOCALDOMAIN)
			RaiseTextError(L"No NTDS for a local domain-- net domains only");

		if(!(Di.Flags & (DOMAIN_INFO_HAS_HOST | DOMAIN_INFO_HAS_FORESTNAME)))
		{
			OpText Ot(L"Looking up a directory domain controller", Di.DomainName);

			RaiseOnWin32Error(DsGetDcName(Di.SearchHost, Di.DomainName, NULL, NULL, static_cast<ULONG>(Opts->Opts[OiDsnameopts].Enum->Choice), DcInfo.assign_indirect()));

			if(!(DcInfo->Flags & DS_DS_FLAG))
				RaiseTextError(L"Domain has no directory service");

			if(!Host)
				Host = Netapi::SkipAnyLeadingBackslashes(DcInfo->DomainControllerName);

			if(!ForestName)
				ForestName = DcInfo->DnsForestName;

			Ot;
		}

		Lc = new LdapConnection(Module, Host, Di.DomainName, ForestName);

		if(!BaseDn)
			this->BaseDn = Lc->DomainDc;
	}

protected:
	virtual ContainerEnumerator<SID> *CreateChild(PCWSTR BaseDn) override
	{
		return new AccountEnumerator(Lc, BaseDn);
	}

public:
	virtual void WalkEntries(EntriesWalkDest<SID> &Dest) override
	{
		auto_free<LDAPMessage, &free_ldap_message> SearchResult;
		PWSTR Attrs[] = {L"cn", L"dc", L"objectSid", NULL};
		LDAPControl *ServerCtrls[] = {&DomainScopeHintCtrl, NULL};

		ULONG OpNum = StartLdapSearchEx(Lc, LDAP_SCOPE_BASE, Attrs, ServerCtrls, NULL, BaseDn, SbtLiteral, L"(objectSid=*)");

		AwaitSearchResult(Lc, OpNum, SearchResult);

		LDAPMessage *Entry = ldap_first_entry(Lc->ld, SearchResult);

		while(Entry)
		{
			auto_free<PWSTR, free_ldap_value> CnValues = auto_take_(ldap_get_values(Lc->ld, Entry, L"cn"));
			auto_free<PWSTR, free_ldap_value> DcValues = auto_take_(ldap_get_values(Lc->ld, Entry, L"dc"));
			auto_free<berval *, free_ldap_value_len> SidValues = auto_take_(ldap_get_values_len(Lc->ld, Entry, L"objectSid"));

			SID &Sid = *c_cast<SID *>(SidValues[0]->bv_val);

			if(CnValues)
				Dest.EntryProc(CnValues[0], Sid);
			else if(DcValues)
				Dest.EntryProc(DcValues[0], Sid);
			else //SidValues is req'd
			{
				auto_szpod<WCHAR> SidText = SidToText(&Sid);

				Dest.EntryProc(SidText, Sid);
			}

			Entry = ldap_next_entry(Lc->ld, Entry);
		}
	}
};

struct DomainAccountEnumerator : Lookup::DomainAccountEnumerator
{
	auto_refct<NtdsLdapModuleBase> Module;

	DomainAccountEnumerator(NtdsLdapModuleBase *Module) :
		Module(Module)
	{}

	virtual Enumerator<SID> *NewEnum(DOMAIN_INFO const &Di) override
	{
		return new AccountEnumerator(Module, Di);
	}
};

}//namespace Ldap